1 module serve;
2 
3 import arsd.cgi;
4 import std.stdio;
5 import std.file;
6 import std.string;
7 import std.process;
8 
9 // -Wl,--export=__heap_base
10 
11 // https://github.com/skoppe/wasm-sourcemaps
12 
13 enum DEFAULT_PATH="build";
14 private __gshared string startPath;
15 
16 string extendedContentTypeFromFileExtension(string thing)
17 {
18 	string ret = contentTypeFromFileExtension(thing);
19 	if(ret != null)
20 		return ret;
21 
22 	if(ret.endsWith(".ogg"))
23 		return "application/ogg";
24 	if(ret.endsWith(".mp4"))
25 		return "application/mp4";
26 
27 	return "application/octet-stream";
28 }
29 
30 import core.thread;
31 import core.sync.mutex;
32 void pushWebsocketMessage(string message)
33 {
34 	synchronized
35 	{
36 		foreach(ref conn; connections)
37 			conn.messages~= message;
38 	}
39 	// socket.send(message);
40 	// synchronized websocketMessages~= message;
41 }
42 
43 struct Connection
44 {
45 	size_t id;
46 	string[] messages;
47 }
48 private __gshared size_t id;
49 private __gshared Connection*[] connections;
50 
51 void serveGameFiles(Cgi cgi) 
52 {
53     import std.path;
54     string targetPath = buildNormalizedPath(startPath, DEFAULT_PATH);
55 	string contentType = extendedContentTypeFromFileExtension(cgi.pathInfo);
56 	string file = buildNormalizedPath(targetPath, cgi.pathInfo[1..$]);
57 
58 	if(cgi.websocketRequested())
59 	{
60 		auto socket = cgi.acceptWebsocket();
61 		Connection conn = Connection(id);
62 		synchronized
63 		{
64 			connections~= &conn;
65 			id++;
66 		}
67 		enum __updateMsg = 0xff;
68 		auto msg = socket.recv();
69 		while(msg.opcode != WebSocketOpcode.close) 
70 		{
71 			synchronized
72 			{
73 				foreach(socketMsg; conn.messages)
74 					socket.send(socketMsg);
75 				conn.messages.length = 0;
76 			}
77 			msg = socket.recv();
78 		}
79 
80 		synchronized
81 		{
82 			socket.close();
83 			import std.algorithm;
84 			ptrdiff_t index = countUntil!((Connection* c) => c.id == conn.id)(connections);
85 			if(index != -1)
86 			{
87 				connections = connections[0..index]~ connections[index+1..$];
88 			}
89 		}
90 		writeln("AutoReloading WebSocket[", conn.id, "] closed.");
91 	}
92 	else if(cgi.pathInfo == "/") 
93 	{
94 		string indexHTML = buildNormalizedPath(targetPath, "index.html");
95 		if(exists(indexHTML))
96 		{
97 			cgi.write(readText(indexHTML)~"<script> "~import("reload_server.js")~"</script>", true);
98 		}
99 		else
100 		{
101 			// index
102 			string html = "<html><head><title>Hipreme Engine Webassembly Server</title></head><body><ul>";
103 			foreach(string name; dirEntries(targetPath, SpanMode.shallow)) 
104 			{
105 				name = name[targetPath.length..$];
106 				html ~= "<li><a href=\"" ~ name ~ "\">" ~ name ~"</a></li>";
107 			}
108 			html~= "</body></html>";
109 				cgi.write(html, true);
110 
111 		}
112 	}
113 	else if(contentType)
114 	{
115 		if(!exists(file))
116 		{
117 			cgi.setResponseStatus("404 file not found");
118 		}
119 		else
120 		{
121 			cgi.setResponseContentType(contentType);
122 			cgi.setResponseStatus(200);
123 			if(contentType[0.."text".length] == "text")
124 				cgi.write(readText(file));
125 			else
126 				cgi.write(read(file));
127 			writeln("GET ", file, " 200");
128 		}
129 	}
130 
131 }
132 
133 private RequestServer server;
134 /++
135 	This is the function [GenericMain] calls. View its source for some simple boilerplate you can copy/paste and modify, or you can call it yourself from your `main`.
136 	Params:
137 		fun = Your request handler
138 		CustomCgi = a subclass of Cgi, if you wise to customize it further
139 		maxContentLength = max POST size you want to allow
140 		args = command-line arguments
141 	History:
142 	Documented Sept 26, 2020.
143 +/
144 
145 
146 
147 void hipengineCgiMain(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)
148 (string[] args, string servePath)  if(is(CustomCgi : Cgi)) 
149 {
150     startPath = servePath;
151 	if(tryAddonServers(args))
152 		return;
153 	if(trySimulatedRequest!(fun, CustomCgi)(args))
154 		return;
155 
156 	// you can change the port here if you like
157 	server.listeningPort = 9000;
158 
159 	string host = server.listeningHost;
160 	if(host == "") host = "localhost";
161 	writeln("HipremeEngine Dev Server listening from ", host,":",server.listeningPort);
162 
163 	// then call this to let the command line args override your default
164 	server.configureFromCommandLine(args);
165 
166 
167 	// and serve the request(s).
168 	server.serve!(fun, CustomCgi, maxContentLength)();
169 
170 }
171 
172 void stopServer()
173 {
174     import core.stdc.stdlib;
175 	pushWebsocketMessage("close");
176     exit(0);
177 }